#define WPCAP
#define HAVE_REMOTE

#define DUMP_UNKNOWN

#include <iostream>
#include <fstream>

#include "pcap.h"
#include "Opcodes.h"

using namespace std;

/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

int main()
{
	pcap_if_t *alldevs;
	pcap_if_t *d;
	int inum;
	int i=0;
	pcap_t *adhandle;
	char errbuf[PCAP_ERRBUF_SIZE];
	bpf_u_int32 netmask;
	struct bpf_program fcode;
	char packet_filter[] = "tcp port 4003";


    
    /* Retrieve the device list on the local machine */
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }
    
    /* Print the list */
    for(d=alldevs; d; d=d->next)
    {
        printf("%d. %s", ++i, d->name);
        if (d->description)
            printf(" (%s)\n", d->description);
        else
            printf(" (No description available)\n");
    }
    
    if(i==0)
    {
        printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
        return -1;
    }
    
    printf("Enter the interface number (1-%d):",i);
    scanf("%d", &inum);
    
    if(inum < 1 || inum > i)
    {
        printf("\nInterface number out of range.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }
    
    /* Jump to the selected adapter */
    for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);

    
    /* Open the device */
    if ( (adhandle= pcap_open(d->name,          // name of the device
                              65536,            // portion of the packet to capture
                                                // 65536 guarantees that the whole packet will be captured on all the link layers
                              PCAP_OPENFLAG_PROMISCUOUS,    // promiscuous mode
                              1000,             // read timeout
                              NULL,             // authentication on the remote machine
                              errbuf            // error buffer
                              ) ) == NULL)
    {
        fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }



	if (d->addresses != NULL)
    /* Retrieve the mask of the first address of the interface */
    netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
		else
    /* If the interface is without an address we suppose to be in a C class network */
    netmask=0xffffff; 


	//compile the filter
	if(pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
	{
		fprintf(stderr,"\nError compiling filter: wrong syntax.\n");
		pcap_close(adhandle);
		return -3;
	}

	//set the filter
	if(pcap_setfilter(adhandle, &fcode)<0)
	{
		fprintf(stderr,"\nError setting the filter\n");
		pcap_close(adhandle);
		return -4;
	}


    
    printf("\nlistening on %s...\n", d->description);
    
    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);
    
    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);
    
    return 0;
}

/* 4 bytes IP address */
typedef struct ip_address{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;


/* IPv4 header */
typedef struct ip_header{
    u_char  ver_ihl;        // Version (4 bits) + Internet header length (4 bits)
    u_char  tos;            // Type of service 
    u_short tlen;           // Total length 
    u_short identification; // Identification
    u_short flags_fo;       // Flags (3 bits) + Fragment offset (13 bits)
    u_char  ttl;            // Time to live
    u_char  proto;          // Protocol
    u_short crc;            // Header checksum
    ip_address  saddr;      // Source address
    ip_address  daddr;      // Destination address
    u_int   op_pad;         // Option + Padding
}ip_header;

/* TCP header */
typedef u_int tcp_seq;
typedef struct tcp_header{
        u_short th_sport;               /* source port */
        u_short th_dport;               /* destination port */
        tcp_seq th_seq;                 /* sequence number */
        tcp_seq th_ack;                 /* acknowledgement number */
        u_char  th_offx2;               /* data offset, rsvd */
		#define TH_OFF(th)	(((th)->th_offx2 & 0xf0) >> 4)
        u_char  th_flags;
        u_short th_win;                 /* window */
        u_short th_sum;                 /* checksum */
        u_short th_urp;                 /* urgent pointer */
}tcp_header;

/* Payload */
typedef struct payload{
        u_short pl_length;				/* payload length */
		u_short pl_magic;				/* magic number */
		u_short pl_crc;					/* crc number */
		u_short pl_unknown;				/* purpose tba */
		u_short pl_hiword_key;			/* hiword xor key */
		u_short pl_loword_key;			/* loword xor key */
		u_short pl_opcode;				/* packet opcode */
		//u_char* pl_data;				/* Data segment of packet */
		//u_short pl_unknown2;			/* purpose tba, not set during cast */
}payload;


void packet_dump(char* filename, char* data, streamsize length)
{
	ofstream f;
	f.open(filename, ios_base::binary | ios_base::out);
	if (!f.is_open())
	{
		printf("Error writing to file: %s", filename);
		return;
	}
	f.write((char *)data, length);
	f.close();
}

void Decrypt(const payload* pl, const u_char* in, u_char* out)
{
	unsigned short data_length = pl->pl_length - 14;
	unsigned int key = (pl->pl_hiword_key << 16) + pl->pl_loword_key;

	unsigned char* charkey = (unsigned char *)&key;
	for (int i = 0; i < data_length; i++)
	{
		*(out+i) = *(in+i) ^ *(charkey+(i%4));
	}
	//*(unsigned int *)out = (*(unsigned int *)in->pl_data) ^ key;
}

void out_handler(char* timestr, u_short opcode, u_short hiword_key, payload* pl, u_char* data)
{
	u_short decrypted_op = opcode ^ hiword_key;

	unsigned short data_length = pl->pl_length - 13;

	u_char* decrypted_data = new u_char[data_length];
	decrypted_data[data_length-1] = '\0';

	switch (decrypted_op)
	{
		case WALK_ACTION:
			printf("[%s] - OUT Opcode=0x%X [WALK ACTION] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_N:
			printf("[%s] - OUT Opcode=0x%X [WALK N] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_NE:
			printf("[%s] - OUT Opcode=0x%X [WALK NE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_E:
			printf("[%s] - OUT Opcode=0x%X [WALK E] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_SE:
			printf("[%s] - OUT Opcode=0x%X [WALK SE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_S:
			printf("[%s] - OUT Opcode=0x%X [WALK S] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_SW:
			printf("[%s] - OUT Opcode=0x%X [WALK SW] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_W:
			printf("[%s] - OUT Opcode=0x%X [WALK W] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case WALK_NW:
			printf("[%s] - OUT Opcode=0x%X [WALK NW] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case IDLE:
#ifdef PRINT_IDLE
			printf("[%s] - OUT Opcode=0x%X [IDLE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
#endif
			break;

		case SPELLBOOK_OPEN:
			printf("[%s] - OUT Opcode=0x%X [SPELLBOOK OPEN] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case EQUIPPEDITEMS_OPEN:
			printf("[%s] - OUT Opcode=0x%X [EQUIPPED ITEMS OPEN] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case STATS_UPDATE:
			printf("[%s] - OUT Opcode=0x%X [STATS UPDATE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case ITEMS_UPDATE:
			printf("[%s] - OUT Opcode=0x%X [ITEMS UPDATE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case STANCE_CHANGE:
			printf("[%s] - OUT Opcode=0x%X [STANCE CHANGE] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case MAGIC_TARGET:
			printf("[%s] - OUT Opcode=0x%X [MAGIC TARGET] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case MELEE_TARGET:
			printf("[%s] - OUT Opcode=0x%X [MELEE TARGET] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case PUBLIC_CHAT:
			printf("[%s] - OUT Opcode=0x%X [PUBLIC CHAT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case PRIVATE_CHAT:
			printf("[%s] - OUT Opcode=0x%X [PRIVATE CHAT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case PORTAL:
			printf("[%s] - OUT Opcode=0x%X [PORTAL] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case RESPAWN:
			printf("[%s] - OUT Opcode=0x%X [RESPAWN] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case FLOOR_VIEW:
			printf("[%s] - OUT Opcode=0x%X [FLOOR VIEW] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case NPC_INTERACT:
			printf("[%s] - OUT Opcode=0x%X [NPC INTERACT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case MERCHANT_INTERACT:
			printf("[%s] - OUT Opcode=0x%X [MERCHANT INTERACT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case ATTEMPT_SELL:
			printf("[%s] - OUT Opcode=0x%X [ATTEMPT SELL] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case ATTEMPT_EQUIP:
			printf("[%s] - OUT Opcode=0x%X [ATTEMPT EQUIP] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case PURCHASE_ITEM:
			printf("[%s] - OUT Opcode=0x%X [PURCHASE ITEM] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case SELL_ITEM:
			printf("[%s] - OUT Opcode=0x%X [SELL ITEM] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case CHANGE_FACING:
			printf("[%s] - OUT Opcode=0x%X [CHANGE FACING] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case INSPECT_PLAYER:
			printf("[%s] - OUT Opcode=0x%X [INSPECT PLAYER] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case VIEW_TRADE:
			printf("[%s] - OUT Opcode=0x%X [VIEW PLAYER BAG] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case SET_STATS:
			printf("[%s] - OUT Opcode=0x%X [SET STATS] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case LOGOUT:
			printf("[%s] - OUT Opcode=0x%X [LOGOUT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		case LOOT:
			printf("[%s] - OUT Opcode=0x%X [LOOT] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);
			printf("\n");
			break;

		default:
			printf("[%s] - OUT Opcode=0x%X [???] CryptoKey=0x%X", timestr, decrypted_op, hiword_key);	
#ifdef DUMP_UNKNOWN
			char filename[64];
			timestr[2] = timestr[5] = '-';
			sprintf(filename, "%X_%s.packet", decrypted_op, timestr);
			packet_dump(filename, (char *)pl, pl->pl_length);
			printf(" - DUMPED TO: %X_%s.packet",  decrypted_op, timestr);
#endif
			printf("\n");
			break;
	}

	if (decrypted_op != IDLE)
	{
			Decrypt(pl, data, decrypted_data);
			printf("Decrypted data: %s\n", decrypted_data);
	}
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
    struct tm *ltime;
    char timestr[16];
    time_t local_tv_sec;
	ip_header *ih;
	tcp_header *th;
	payload * pl;

	const int eth_len = 14;

    /* retrieve the position of the ip header */
    ih = (ip_header *) (pkt_data + eth_len);
	int ip_len = (ih->ver_ihl & 0xf) * 4;

	/* retrieve the position of the tcp header */
    th = (tcp_header *) (pkt_data + eth_len + ip_len);
	int tcp_len = TH_OFF(th)*4;

	//discard FUCKING ANNOYING packets
	if (th->th_flags == 16)
	{
		return;
	}

	/* retrieve the position of the payload */
    pl = (payload *) (pkt_data + eth_len + ip_len + tcp_len);
	int pl_len = pl->pl_length;
	u_char* pl_data = (u_char *)(pkt_data + eth_len + ip_len + tcp_len + 14);

    /* convert the timestamp to readable format */
    local_tv_sec = header->ts.tv_sec;
    ltime=localtime(&local_tv_sec);
    strftime( timestr, sizeof timestr, "%H:%M:%S", ltime );
    
	bool printInbound = false;
	bool printOutbound = true;

	int cryptoKey = pl->pl_hiword_key;
	cryptoKey <<= 16;
	cryptoKey += pl->pl_loword_key;
		

	if( ih->saddr.byte1 == 192 )
	{
		if(printOutbound)
		{
			out_handler(timestr, pl->pl_opcode, pl->pl_hiword_key, pl, pl_data);
		}
	}
	else
	{
		if(printInbound)
		{
			printf("[%s] - IN Opcode=0x%X CryptoKey=0x%X", timestr, pl->pl_opcode^pl->pl_hiword_key, cryptoKey);
			printf("\n");
		}
	}
}




// 